﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Core.Tools
{
    public sealed class AgentEnumerable<In, Out> : IEnumerable<Out>, ICollection, ICollection<Out>
    {
        private readonly IEnumerable<In> InternalEnumerable;
        private readonly Func<In, Out> InternalAsValue;
        private int InternalCount;

        public AgentEnumerable(IEnumerable<In> Enumerable, Func<In, Out> AsValue, int Count = -1)
        {
            InternalCount = Count;
            InternalEnumerable = Enumerable;
            InternalAsValue = AsValue;
        }

        public AgentEnumerable(In[] Array, Func<In, Out> AsValue)
        {
            InternalCount = Count;
            InternalEnumerable = Array;
            InternalAsValue = AsValue;
        }

        public int Count
        {
            get
            {
                if (InternalCount == -1)
                {
                    InternalCount = this.Count();
                }

                return InternalCount;
            }
        }

        public IEnumerator<Out> GetEnumerator()
        {
            return new Enumerator(this);
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        public void CopyTo(Array Array, int Index)
        {
            if (Array == null)
            {
                throw new ArgumentNullException("array");
            }

            if (Index < 0)
            {
                throw new ArgumentOutOfRangeException("index");
            }

            int Length = Array.Length - Index;

            if (Length < Count)
            {
                throw new ArgumentException("Length");
            }

            if (Array.Rank != 1)
            {
                throw new ArgumentException("Rank");
            }

            if (Array is Out[])
            {
                var TypedArray = (Out[])Array;

                foreach (var Item in this)
                {
                    TypedArray[Index] = Item;
                    ++Index;
                }
            }
            else
            {
                foreach (var Item in this)
                {
                    Array.SetValue(Item, Index);
                    ++Index;
                }
            }
        }

        object ICollection.SyncRoot
        {
            get
            {
                return null;
            }
        }

        bool ICollection.IsSynchronized
        {
            get
            {
                return false;
            }
        }

        void ICollection<Out>.Add(Out Item)
        {
            throw new NotSupportedException("Add");
        }

        void ICollection<Out>.Clear()
        {
            throw new NotSupportedException("Clear");
        }

        bool ICollection<Out>.Contains(Out Item)
        {
            var Comparer = EqualityComparer<Out>.Default;

            foreach (var Element in this)
            {
                if (Comparer.Equals(Element, Item))
                {
                    return true;
                }
            }

            return false;
        }

        public void CopyTo(Out[] Array, int ArrayIndex)
        {
            CopyTo(Array, ArrayIndex);
        }

        bool ICollection<Out>.Remove(Out item)
        {
            throw new NotSupportedException("Remove");
        }

        bool ICollection<Out>.IsReadOnly
        {
            get
            {
                return true;
            }
        }


        private sealed class Enumerator : IEnumerator<Out>
        {
            private readonly IEnumerator<In> InternalContent;
            private readonly Func<In, Out> InternalAsValue;

            public Enumerator(AgentEnumerable<In, Out> Base)
            {
                InternalAsValue = Base.InternalAsValue;
                InternalContent = Base.InternalEnumerable.GetEnumerator();
            }

            public Out Current
            {
                get
                {
                    return InternalAsValue(InternalContent.Current);
                }
            }

            object IEnumerator.Current
            {
                get
                {
                    return Current;
                }
            }

            public void Dispose()
            {
                InternalContent.Dispose();
            }

            public bool MoveNext()
            {
                return InternalContent.MoveNext();
            }

            public void Reset()
            {
                InternalContent.Reset();
            }
        }
    }

    public class ElementEnumerable<Out> : IEnumerable<Out>
    {
        private readonly IEnumerable Elements;
        private readonly Func<object, Out> AsValue;

        public ElementEnumerable(IEnumerable Elements, Func<object, Out> AsValue)
        {
            this.Elements = Elements;
            this.AsValue = AsValue;
        }
        public IEnumerator<Out> GetEnumerator()
        {
            return new Enumerator(this);
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        public class Enumerator : IEnumerator<Out>
        {
            private readonly IEnumerator Content;
            private readonly ElementEnumerable<Out> Base;

            public Enumerator(ElementEnumerable<Out> Base)
            {
                this.Base = Base;
                Content = Base.Elements.GetEnumerator();
            }

            public Out Current => Base.AsValue(Content.Current);

            object IEnumerator.Current => Current;

            public void Dispose()
            {
            }

            public bool MoveNext()
            {
                return Content.MoveNext();
            }

            public void Reset()
            {
                Content.Reset();
            }
        }
    }
}